#!/usr/bin/env python
# -*- coding: utf-8 -*-
####################################################################
# File: tv_wx.py
# Author: Jean-Marc VARENNE-PAQUET
# Description: This script is a graphical user interface (GUI), 
# that allows users to watch TV, by using the multiposte service 
# from the ADSL and fiber boxes.
# Version: 1.1
# License: GNU GPL v3
####################################################################



# Import
import os, sys, string, datetime, re, urllib2
import wx


# Initialization
g_channel_name_list = []
g_channel_urls_hash_table = {}
g_channel_programs_hash_table = {}
g_favorite_channel_name_list = []
g_vlc = 'vlc'
g_configuration_directory = os.environ['HOME'] + "/.config/x_multipost_tv"
g_playlist_url_file_path = g_configuration_directory + "/playlist_url.txt"
g_favorites_file_path = g_configuration_directory + "/favorit_channels.txt"
g_tasks_file = g_configuration_directory + "/crontab_tasks.txt"
g_playlist_url = "http://mafreebox.freebox.fr/freeboxtv/playlist.m3u"










##########################################################
# Classes
##########################################################

# The main window
class Ui_MainWindow(wx.Frame):

	# Constructor
	def __init__(self):
		wx.Frame.__init__(self, None, -1, "X-Multiposte-TV", pos=wx.DefaultPosition, size=(450,750))



		#########################################################################################
		# Menu bar
		#########################################################################################

		# Create menu bar
		menu_bar = wx.MenuBar()
		self.SetMenuBar(menu_bar)

		# menu File
		menu_file = wx.Menu()
		menu_bar.Append(menu_file, "Fichier")
		menu_file.Append(1, "Ouvrir un enregistrement video... \tCtrl-O")
		self.Bind(wx.EVT_MENU, self.openLocalVideo, id=1)
		menu_file.AppendSeparator()
		menu_file.Append(2, "Quitter \tCtrl-Q")
		self.Bind(wx.EVT_MENU, lambda event : self.Close(), id=2)

		# menu TV guide
		menu_tv_guide = wx.Menu()
		menu_bar.Append(menu_tv_guide, "GuideTV")
		menu_tv_guide.Append(3, "Ouvrir le guide TV")
		self.Bind(wx.EVT_MENU, lambda event : wx.LaunchDefaultBrowser("http://streamplayer.free.fr/tv/guidetv.php"), id=3)

		# menu Configuration
		menu_configuration = wx.Menu()
		menu_bar.Append(menu_configuration, "Configuration")
		menu_configuration.Append(4, "Definir la playlist a utiliser")
		self.Bind(wx.EVT_MENU, self.displayConfigurationWindow, id=4)

		# menu Help
		menu_help = wx.Menu()
		menu_bar.Append(menu_help, "Aide")
		menu_help.Append(5, "A propos de cette application")
		self.Bind(wx.EVT_MENU, self.displayAboutAppWindow, id=5)




		#########################################################################################
		# Central Widget
		#########################################################################################

		# Create a notebook.
		note_book = wx.Notebook(self)


		# Page 1
		page_1 = wx.Panel(note_book)
		note_book.AddPage(page_1, "Toutes")
		self.entry_search = wx.TextCtrl(page_1)
		self.Bind(wx.EVT_TEXT, lambda event : self.updateModel(), self.entry_search)
		self.list_box_page_1 = wx.ListBox(page_1)
		self.list_box_page_1.Clear()
		self.list_box_page_1.Bind(wx.EVT_LISTBOX_DCLICK, self.onListBox1DoubleClick)
		self.list_box_page_1.Bind(wx.EVT_RIGHT_UP, self.onListBox1RightClick)
		sizer = wx.BoxSizer(wx.VERTICAL)
		sizer.Add(self.entry_search   , 0        , wx.EXPAND)
		sizer.Add(self.list_box_page_1, wx.EXPAND, wx.EXPAND)
		page_1.SetSizer(sizer)



		# Page 2
		page_2 = wx.Panel(note_book)
		note_book.AddPage(page_2, "Favorites")
		self.list_box_page_2 = wx.ListBox(page_2)
		self.list_box_page_2.Clear()
		self.list_box_page_2.Bind(wx.EVT_LISTBOX_DCLICK, self.onListBox2DoubleClick)
		self.list_box_page_2.Bind(wx.EVT_RIGHT_UP, self.onListBox2RightClick)
		sizer = wx.BoxSizer(wx.VERTICAL)
		sizer.Add(self.list_box_page_2, wx.EXPAND, wx.EXPAND)
		page_2.SetSizer(sizer)




		#########################################################################################
		# Create a directory for the configuration files
		#########################################################################################

		if not os.path.exists(g_configuration_directory):
			try:
				os.makedirs(g_configuration_directory)
			except OSError as msg:
				print(msg)




		#########################################################################################
		# Load data
		#########################################################################################

		self.readConfigurationFile()
		self.loadPlaylist()
		self.reloadFavoritesFile()

		self.entry_search.SetFocus()

	# On double click on ListBox 1.
	def onListBox1DoubleClick(self, event):
		channel_name = self.list_box_page_1.GetStringSelection()
		self.watchTV(channel_name)

	# On double click on ListBox 2.
	def onListBox2DoubleClick(self, event):
		channel_name = self.list_box_page_2.GetStringSelection()
		self.watchTV(channel_name)

	# Display a context menu. This one is called by a right click on a channel name in the main channels list.
	def onListBox1RightClick(self, event):
		position = event.GetPosition()
		channel_name = self.list_box_page_1.GetStringSelection()

		menu_context = wx.Menu()
		menu_context.Append(6, channel_name)
		menu_context.AppendSeparator()
		menu_context.Append(7, "Regarder")
		self.Bind(wx.EVT_MENU, lambda event : self.watchTV(channel_name), id=7)
		menu_context.Append(8, "Enregistrer et regarder")
		self.Bind(wx.EVT_MENU, lambda event : self.watchAndRecordTV(channel_name), id=8)
		menu_context.Append(9, "Planifier un enregistrement")
		self.Bind(wx.EVT_MENU, lambda event : self.planRecord(channel_name), id=9)
		menu_context.Append(10, "Ouvrir le guide TV sur cette chaine")
		self.Bind(wx.EVT_MENU, lambda event : self.openWebTVGuide(channel_name), id=10)
		menu_context.Append(11, "Ajouter aux favoris")
		self.Bind(wx.EVT_MENU, lambda event : self.addToFavorites(channel_name), id=11)

		self.PopupMenu(menu_context, position)

	# Display a context menu. This one is called by a right click on a channel name in the favorite channels list.
	def onListBox2RightClick(self, event):
		position = event.GetPosition()
		channel_name = self.list_box_page_2.GetStringSelection()

		menu_context = wx.Menu()
		menu_context.Append(6, channel_name)
		menu_context.AppendSeparator()
		menu_context.Append(7, "Regarder")
		self.Bind(wx.EVT_MENU, lambda event : self.watchTV(channel_name), id=7)
		menu_context.Append(8, "Enregistrer et regarder")
		self.Bind(wx.EVT_MENU, lambda event : self.watchAndRecordTV(channel_name), id=8)
		menu_context.Append(9, "Planifier un enregistrement")
		self.Bind(wx.EVT_MENU, lambda event : self.planRecord(channel_name), id=9)
		menu_context.Append(10, "Ouvrir le guide TV sur cette chaine")
		self.Bind(wx.EVT_MENU, lambda event : self.openWebTVGuide(channel_name), id=10)
		menu_context.Append(11, "Retirer des favoris")
		self.Bind(wx.EVT_MENU, lambda event : self.removeFromFavorites(channel_name), id=11)

		self.PopupMenu(menu_context, position)

	# Update the channels list displayed. It takes into account the filter from the search field. 
	def updateModel(self):
		string = self.entry_search.GetValue()
		self.list_box_page_1.Clear()
		for channel_name in g_channel_name_list:
			minus_channel_name = channel_name.lower()
			minus_string = string.lower()
			if minus_channel_name.find(minus_string) >= 0:
				self.list_box_page_1.Append(channel_name)

	# Run VLC to display a channel.
	def watchTV(self, channel_name):
		channel_url = g_channel_urls_hash_table[channel_name]
		command = g_vlc + " --video-filter=deinterlace --deinterlace-mode=yadif2x \"" + channel_url + "\" &"
		print(command)
		os.system(command)

	# Run VLC to watch and record a channel at the same time.
	def watchAndRecordTV(self, channel_name):
		channel_url = g_channel_urls_hash_table[channel_name]
		output_file_path = os.environ['HOME'] + os.sep + channel_name.encode('utf-8').replace(" ","") + "__" + str(datetime.datetime.today()).replace(":","-").replace(" ","__") + ".ts"
		command = g_vlc + " \"" + channel_url + "\" --sout=\"#duplicate{dst=std{access=file,mux=ts,dst=" + output_file_path + "},dst=display}\" &"
		print(command)
		os.system(command)

	# Display a window which allows to plan a record from a date to an other.
	def planRecord(self, channel_name):
		channel_url = g_channel_urls_hash_table[channel_name]
		self.record_programming_window = Ui_RecordPlanningWindow(self, channel_name, channel_url)
		self.record_programming_window.ShowModal()

	# Open a web site on the corresponding channel TV guide.
	def openWebTVGuide(self, channel_name):
		channel_number = ""
		myPattern = re.compile('^(\d+)')
		m = re.search(myPattern, channel_name)
		try:
			channel_number = m.groups(1)[0]
		except:
			pass
		wx.LaunchDefaultBrowser("http://streamplayer.free.fr/tv/guidetv.php?chaines=freebox-tv#c" + channel_number)

	# Add a channel in the favorite channels list.
	def addToFavorites(self, channel_name):
		global g_favorite_channel_name_list
		g_favorite_channel_name_list.append(channel_name)
		self.updateFavoritesFile()
		self.list_box_page_2.Clear()
		for channel_name in g_favorite_channel_name_list:
			self.list_box_page_2.Append(channel_name)

	# Remove a channel from the favorite channels list.
	def removeFromFavorites(self, channel_name):
		global g_favorite_channel_name_list
		g_favorite_channel_name_list.remove(channel_name)
		self.updateFavoritesFile()
		self.list_box_page_2.Clear()
		for channel_name in g_favorite_channel_name_list:
			self.list_box_page_2.Append(channel_name)

	# Update the favorite channels list file.
	def updateFavoritesFile(self):
		try:
			favorite_channels_file = open(g_favorites_file_path,'w')
			for channel_name in g_favorite_channel_name_list:
				favorite_channels_file.write(channel_name.encode('utf-8') + "\n")
			favorite_channels_file.close()
		except IOError as msg:
			print("ERROR: Cannot write in " + g_favorites_file_path)
			print(msg)
			sys.exit(1)

	# Read configuration file
	def readConfigurationFile(self):
		global g_playlist_url
		try:
			configuration_file = open(g_playlist_url_file_path,'r')
			g_playlist_url = configuration_file.readline()
			configuration_file.close()
		except:
			pass

	# Load the remote Freebox playlist file
	def loadPlaylist(self):
		global g_channel_name_list
		global g_channel_urls_hash_table
		g_channel_name_list = []
		g_channel_urls_hash_table = {}

		playlist_file_lines = []
		try:
			playlist_file = urllib2.urlopen(g_playlist_url)
			playlist_file_lines = playlist_file.readlines()
			playlist_file.close()
		except urllib2.URLError as msg:
			print("WARNING: Cannot open playlist " + g_playlist_url)
			print(msg)
			message_dialog = wx.MessageDialog(self, 'La playlist suivante n\'est pas accessible : \n' + g_playlist_url, 'Attention', style=wx.ICON_WARNING)
			message_dialog.ShowModal()

		channel_name = ""
		for line in playlist_file_lines:
			if line.startswith('#EXTINF:'):
				splitted_line = string.split(line,",")
				channel_name = splitted_line[1].rstrip()
				channel_name = channel_name.decode('utf-8')
				g_channel_name_list.append(channel_name)
			if line.startswith('rtsp:'):
				channel_url = line.rstrip()
				g_channel_urls_hash_table[channel_name] = channel_url

		self.updateModel()

	# Reload the favorites file
	def reloadFavoritesFile(self):
		global g_favorite_channel_name_list
		if os.path.exists(g_favorites_file_path):
			# Get favorite channels list from the file.
			g_favorite_channel_name_list = []
			file_lines = []
			try:
				favorite_channels_file = open(g_favorites_file_path, 'r')
				file_lines = favorite_channels_file.readlines()
				favorite_channels_file.close()
			except IOError as msg:
				print("ERROR: Cannot read " + g_favorites_file_path)
				print(msg)
				sys.exit(1)

			# Update the favorite channels list and display it.
			self.list_box_page_2.Clear()
			for line in file_lines:
				line = line.rstrip()
				channel_name = line.decode('utf-8')
				g_favorite_channel_name_list.append(channel_name)
				self.list_box_page_2.Append(channel_name)

	# Open a window to select a video record from the hard disk.
	def openLocalVideo(self, event):
		dialog_window = wx.FileDialog(self, "Charger une video", os.environ['HOME'], "", "*.ts", wx.OPEN)
		if dialog_window.ShowModal() == wx.ID_OK:
			dirname = dialog_window.GetDirectory()
			filename = dialog_window.GetFilename()
			local_video_file_path = os.path.join(dirname, filename).encode('utf-8')
			command = g_vlc + " --video-filter=deinterlace --deinterlace-mode=yadif2x \"" + local_video_file_path + "\" &"
			print(command)
			os.system(command)

	# Open a window which displays information about the script.
	def displayAboutAppWindow(self, event):
		info = wx.AboutDialogInfo()
		info.SetName(self.GetTitle())
		info.SetDescription("Logiciel libre, pour transformer son PC Linux en TV. Ce logiciel exploite le service TV multiposte du fournisseur d'accès à Internet.")
		me = "Jean-Marc VARENNE-PAQUET"
		info.AddDeveloper(me)
		info.AddDocWriter(me)
		info.SetCopyright("Sous licence GNU GPL v3")
		about_window = wx.AboutBox(info)

	# Display a window to set the playlist to use.
	def displayConfigurationWindow(self, event):
		configuration_window = Ui_ConfigurationWindow(self)
		configuration_window.ShowModal()
		self.loadPlaylist()







# The window to set the playlist to use.
class Ui_ConfigurationWindow(wx.Dialog):

	# Constructor
	def __init__(self, parent=None):
		wx.Dialog.__init__(self, parent, title="Definir une playlist a utiliser" ,size=(450,100))

		sizerv = wx.BoxSizer(wx.VERTICAL)

		# Entry.
		self.entry_field = wx.TextCtrl(self)
		self.entry_field.SetValue(g_playlist_url)
		sizerv.Add(self.entry_field, 0, wx.EXPAND)

		# Ok, cancel buttons.
		okButton = wx.Button(self, label='Ok')
		okButton.Bind(wx.EVT_BUTTON, self.updateConfigurationFile)
		cancelButton = wx.Button(self, label='Cancel')
		cancelButton.Bind(wx.EVT_BUTTON, lambda event : self.Close())
		btnsizer = wx.StdDialogButtonSizer()
		sizerv.Add(btnsizer, 0, wx.ALIGN_CENTRE | wx.ALL, 20)
		btnsizer.SetAffirmativeButton(okButton)
		btnsizer.SetCancelButton(cancelButton)
		btnsizer.Realize()
		
		self.SetSizer(sizerv)

	# Update the configuration file with the new playlist
	def updateConfigurationFile(self, event):
		global g_playlist_url
		g_playlist_url = self.entry_field.GetValue()
		try:
			configuration_file = open(g_playlist_url_file_path,'w')
			configuration_file.write(g_playlist_url)
			configuration_file.close()
		except IOError as msg:
			print("WARNING: Cannot write configuration file " + g_playlist_url_file_path)
			print(msg)
		self.Destroy()






# The window to plan video records.
class Ui_RecordPlanningWindow(wx.Dialog):

	# Constructor
	def __init__(self, parent=None, channel_name="", channel_url=""):
		wx.Dialog.__init__(self, parent, title="Enregistrement" ,size=(300,200))
		self.channel_name = channel_name
		self.channel_url = channel_url
		date_time_format = "%Y-%m-%d %H:%M"

		sizerv = wx.BoxSizer(wx.VERTICAL)


		# Display channel name.
		label_channel_name = wx.StaticText(self)
		label_channel_name.SetLabel("Enregistrement de " + self.channel_name)
		sizerv.Add(label_channel_name, 0, wx.EXPAND)

		# Start date
		label_start = wx.StaticText(self)
		label_start.SetLabel("Date de debut")
		sizerv.Add(label_start, 0, wx.EXPAND)

		self.entry_start = wx.TextCtrl(self)
		self.entry_start.SetValue(str(datetime.datetime.now().strftime(date_time_format)))
		sizerv.Add(self.entry_start, 0, wx.EXPAND)


		# End date
		label_end = wx.StaticText(self)
		label_end.SetLabel("Date de fin")
		sizerv.Add(label_end, 0, wx.EXPAND)

		self.entry_end = wx.TextCtrl(self)
		self.entry_end.SetValue(str(datetime.datetime.now().strftime(date_time_format)))
		sizerv.Add(self.entry_end, 0, wx.EXPAND)


		# Ok, cancel buttons.
		okButton = wx.Button(self, label='Ok')
		okButton.Bind(wx.EVT_BUTTON, self.planRecord)
		cancelButton = wx.Button(self, label='Cancel')
		cancelButton.Bind(wx.EVT_BUTTON, lambda event : self.Close())
		btnsizer = wx.StdDialogButtonSizer()
		sizerv.Add(btnsizer, 0, wx.ALIGN_CENTRE | wx.ALL, 20)
		btnsizer.SetAffirmativeButton(okButton)
		btnsizer.SetCancelButton(cancelButton)
		btnsizer.Realize()
		
		self.SetSizer(sizerv)

	# Use the contab to plan a record between the two dates.
	def planRecord(self, event):
		start_date = self.entry_start.GetValue()
		print("Start date: " + start_date)
		end_date = self.entry_end.GetValue()
		print("End date: " + end_date)

		# Convert dates format to fit the crontab format: mm hh jj MMM JJJ task
		tmp_datetime = datetime.datetime.strptime(start_date, "%Y-%m-%d %H:%M")
		start_date_for_cron = tmp_datetime.strftime("%M %H %d %m") + " *"
		print("Start date for crontab: " + start_date_for_cron)
		tmp_datetime = datetime.datetime.strptime(end_date, "%Y-%m-%d %H:%M")
		end_date_for_cron = tmp_datetime.strftime("%M %H %d %m") + " *"
		print("End date for crontab: " + end_date_for_cron)

		# Use the contab to plan a record between the two dates.
		output_file_path = os.environ['HOME'] + os.sep + self.channel_name.replace(" ","") + "__" + start_date.replace(" ", "__").replace(":", "-") + ".ts"
		output_file_path = output_file_path.encode('utf-8')
		print(output_file_path)

		# Write a temporary file with the crontab tasks.
		try:
			tasks_file = open(g_tasks_file,'w')
			tasks_file.write(start_date_for_cron + " " + g_vlc + " \"" + self.channel_url + "\" --intf dummy --sout file/ts:" + output_file_path + "\n")
			tasks_file.write(end_date_for_cron + " killall vlc && crontab -r\n")
			tasks_file.close()
		except IOError as msg:
			print("ERROR: Cannot write in " + g_tasks_file)
			print(msg)
			sys.exit(1)

		# Load the temporary file in crontab.
		command = "crontab " + g_tasks_file
		os.system(command)
		self.Destroy()










##########################################################
# Main
##########################################################

if __name__ == "__main__":

	# Display the main window
	app = wx.PySimpleApp()
	main_window = Ui_MainWindow()
	main_window.Centre()
	main_window.Show()
	app.MainLoop()



